Micron Document
🎖️GitЯра🎖️

Commit d9fe1691aca32d7df51a3a41a7a436b126547f1e


Parents : f2769a3
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-06-16T21:04:06-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-06-17T02:04:06Z

feat(export): add hop start and relay node columns to CSV export (#5822)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

Changes
Diff

diff --git a/core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ExportDataUseCase.kt b/core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ExportDataUseCase.kt
index cf30a41a5f..1759027caa 100644
--- a/core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ExportDataUseCase.kt
+++ b/core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ExportDataUseCase.kt
@@ -37,6 +37,12 @@ constructor(
private val nodeRepository: NodeRepository,
private val meshLogRepository: MeshLogRepository,
) {
+ companion object {
+ private const val BYTE_MASK = 0xFF
+ private const val HEX_PAD_WIDTH = 2
+ private const val HEX_RADIX = 16
+ }
+
/**
* Writes all persisted packet data to the provided [BufferedSink].
*
@@ -55,7 +61,7 @@ constructor(
@Suppress("MaxLineLength")
sink.writeUtf8(
- "\"date\",\"time\",\"from\",\"sender name\",\"sender lat\",\"sender long\",\"rx lat\",\"rx long\",\"rx elevation\",\"rx snr\",\"distance(m)\",\"hop limit\",\"payload\"\n",
+ "\"date\",\"time\",\"from\",\"sender name\",\"sender lat\",\"sender long\",\"rx lat\",\"rx long\",\"rx elevation\",\"rx snr\",\"distance(m)\",\"hop limit\",\"hop start\",\"relay node\",\"payload\"\n",
)
meshLogRepository.getAllLogsInReceiveOrder(Int.MAX_VALUE).first().forEach { packet ->
@@ -101,6 +107,15 @@ constructor(
}
val hopLimit = proto.hop_limit
+ // hop_start lets a reader derive hops-away (hop_start - hop_limit) alongside hop_limit.
+ val hopStart = proto.hop_start
+ // relay_node carries only the last byte of the relaying node's NodeNum (0 means unset).
+ // Emit it as a hex byte so it can be matched against the tail of a node id (e.g. !a1b2c3d4 ->
+ // "d4").
+ val relayNode =
+ proto.relay_node
+ .takeIf { it != 0 }
+ ?.let { (it and BYTE_MASK).toString(HEX_RADIX).padStart(HEX_PAD_WIDTH, '0') } ?: ""
val decoded = proto.decoded
val encrypted = proto.encrypted
val payload =
@@ -118,7 +133,7 @@ constructor(
@Suppress("MaxLineLength")
sink.writeUtf8(
- "$rxDateTime,\"$rxFrom\",\"$senderName\",\"$senderLat\",\"$senderLong\",\"$rxLat\",\"$rxLong\",\"$rxAlt\",\"$rxSnr\",\"$dist\",\"$hopLimit\",\"$payload\"\n",
+ "$rxDateTime,\"$rxFrom\",\"$senderName\",\"$senderLat\",\"$senderLong\",\"$rxLat\",\"$rxLong\",\"$rxAlt\",\"$rxSnr\",\"$dist\",\"$hopLimit\",\"$hopStart\",\"$relayNode\",\"$payload\"\n",
)
}
}

diff --git a/core/domain/src/commonTest/kotlin/org/meshtastic/core/domain/usecase/settings/ExportDataUseCaseTest.kt b/core/domain/src/commonTest/kotlin/org/meshtastic/core/domain/usecase/settings/ExportDataUseCaseTest.kt
index edb547b649..96bc13c7de 100644
--- a/core/domain/src/commonTest/kotlin/org/meshtastic/core/domain/usecase/settings/ExportDataUseCaseTest.kt
+++ b/core/domain/src/commonTest/kotlin/org/meshtastic/core/domain/usecase/settings/ExportDataUseCaseTest.kt
@@ -50,6 +50,7 @@ class ExportDataUseCaseTest {
val output = buffer.readUtf8()
assertTrue(output.startsWith("\"date\",\"time\",\"from\""))
+ assertTrue(output.contains("\"hop limit\",\"hop start\",\"relay node\",\"payload\""))
}
@Test
@@ -79,4 +80,63 @@ class ExportDataUseCaseTest {
assertTrue(output.contains("\"1234\""))
assertTrue(output.contains("Hello"))
}
+
+ @Test
+ fun `invoke writes hop start and relay node columns`() = runTest {
+ val buffer = Buffer()
+ val log =
+ MeshLog(
+ uuid = "1",
+ message_type = "TEXT",
+ received_date = 1000000000L,
+ raw_message = "",
+ fromRadio =
+ FromRadio(
+ packet =
+ MeshPacket(
+ from = 1234,
+ rx_snr = 5.0f,
+ hop_limit = 2,
+ hop_start = 3,
+ relay_node = 77, // 0x4d -> matches the tail of a node id such as !........4d
+ decoded = Data(portnum = PortNum.TEXT_MESSAGE_APP, payload = "Hello".encodeUtf8()),
+ ),
+ ),
+ )
+ meshLogRepository.setLogs(listOf(log))
+
+ useCase(buffer, 1)
+
+ val output = buffer.readUtf8()
+ // hop limit, hop start, relay node (hex), payload
+ assertTrue(output.contains("\"2\",\"3\",\"4d\",\"Hello\""))
+ }
+
+ @Test
+ fun `invoke leaves relay node blank when unset`() = runTest {
+ val buffer = Buffer()
+ val log =
+ MeshLog(
+ uuid = "1",
+ message_type = "TEXT",
+ received_date = 1000000000L,
+ raw_message = "",
+ fromRadio =
+ FromRadio(
+ packet =
+ MeshPacket(
+ from = 1234,
+ rx_snr = 5.0f,
+ decoded = Data(portnum = PortNum.TEXT_MESSAGE_APP, payload = "Hello".encodeUtf8()),
+ ),
+ ),
+ )
+ meshLogRepository.setLogs(listOf(log))
+
+ useCase(buffer, 1)
+
+ val output = buffer.readUtf8()
+ // relay_node defaults to 0 (unset) -> blank field before the payload
+ assertTrue(output.contains("\"0\",\"\",\"Hello\""))
+ }
}

Served by rngit 1.5.0 - Generated in 0.08s